/*
 * Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.encoding;

import java.nio.ByteBuffer;


import com.sun.corba.se.impl.encoding.BufferManagerWrite;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.pept.transport.ByteBufferPool;
import com.sun.corba.se.spi.orb.ORB;

// Notes about the class.
// Assumptions, the ByteBuffer's position is set by the constructor's
// index variable and the ByteBuffer's limit points to the end of the
// data. Also, since the index variable tracks the current empty
// position in the buffer, the ByteBuffer's position is updated
// any time there's a call to this class's position().
// Although, a ByteBuffer's length is it's capacity(), the context in
// which length is used in this object, this.buflen is actually the
// ByteBuffer limit().

public class ByteBufferWithInfo {

  private ORB orb;
  private boolean debug;
  // REVISIT - index should eventually be replaced with byteBuffer.position()
  private int index;     // Current empty position in buffer.
  // REVISIT - CHANGE THESE TO PRIVATE
  public ByteBuffer byteBuffer;// Marshal buffer.
  public int buflen;     // Total length of buffer. // Unnecessary...
  public int needed;     // How many more bytes are needed on overflow.
  public boolean fragmented; // Did the overflow operation fragment?

  public ByteBufferWithInfo(org.omg.CORBA.ORB orb,
      ByteBuffer byteBuffer,
      int index) {
    this.orb = (com.sun.corba.se.spi.orb.ORB) orb;
    debug = this.orb.transportDebugFlag;
    this.byteBuffer = byteBuffer;
    if (byteBuffer != null) {
      this.buflen = byteBuffer.limit();
    }
    position(index);
    this.needed = 0;
    this.fragmented = false;
  }

  public ByteBufferWithInfo(org.omg.CORBA.ORB orb, ByteBuffer byteBuffer) {
    this(orb, byteBuffer, 0);
  }

  public ByteBufferWithInfo(org.omg.CORBA.ORB orb,
      BufferManagerWrite bufferManager) {
    this(orb, bufferManager, true);
  }

  // Right now, EncapsOutputStream's do not use pooled byte buffers.
  // EncapsOutputStream's is the only one that does not use pooled
  // byte buffers. Hence, the reason for the boolean 'usePooledByteBuffers'.
  // See EncapsOutputStream for additional information.

  public ByteBufferWithInfo(org.omg.CORBA.ORB orb,
      BufferManagerWrite bufferManager,
      boolean usePooledByteBuffers) {
    this.orb = (com.sun.corba.se.spi.orb.ORB) orb;
    debug = this.orb.transportDebugFlag;

    int bufferSize = bufferManager.getBufferSize();

    if (usePooledByteBuffers) {
      ByteBufferPool byteBufferPool = this.orb.getByteBufferPool();
      this.byteBuffer = byteBufferPool.getByteBuffer(bufferSize);

      if (debug) {
        // print address of ByteBuffer gotten from pool
        int bbAddress = System.identityHashCode(byteBuffer);
        StringBuffer sb = new StringBuffer(80);
        sb.append("constructor (ORB, BufferManagerWrite) - got ")
            .append("ByteBuffer id (").append(bbAddress)
            .append(") from ByteBufferPool.");
        String msgStr = sb.toString();
        dprint(msgStr);
      }
    } else {
      // don't allocate from pool, allocate non-direct ByteBuffer
      this.byteBuffer = ByteBuffer.allocate(bufferSize);
    }

    position(0);
    this.buflen = bufferSize;
    this.byteBuffer.limit(this.buflen);
    this.needed = 0;
    this.fragmented = false;
  }

  // Shallow copy constructor
  public ByteBufferWithInfo(ByteBufferWithInfo bbwi) {
    this.orb = bbwi.orb;
    this.debug = bbwi.debug;
    this.byteBuffer = bbwi.byteBuffer;
    this.buflen = bbwi.buflen;
    this.byteBuffer.limit(this.buflen);
    position(bbwi.position());
    this.needed = bbwi.needed;
    this.fragmented = bbwi.fragmented;
  }

  // So IIOPOutputStream seems more intuitive
  public int getSize() {
    return position();
  }

  // accessor to buflen
  public int getLength() {
    return buflen;
  }

  // get position in this buffer
  public int position() {
    // REVISIT - This should be changed to return the
    //           value of byteBuffer.position() rather
    //           than this.index. But, byteBuffer.position
    //           is manipulated via ByteBuffer writes, reads,
    //           gets and puts. These locations need to be
    //           investigated and updated before
    //           byteBuffer.position() can be returned here.
    // return byteBuffer.position();
    return index;
  }

  // set position in this buffer
  public void position(int newPosition) {
    // REVISIT - This should be changed to set only the
    //           value of byteBuffer.position rather
    //           than this.index. This change should be made
    //           in conjunction with the change to this.position().
    byteBuffer.position(newPosition);
    index = newPosition;
  }

  // mutator to buflen
  public void setLength(int theLength) {
    buflen = theLength;
    byteBuffer.limit(buflen);
  }

  // Grow byteBuffer to a size larger than position() + needed
  public void growBuffer(com.sun.corba.se.spi.orb.ORB orb) {
    // This code used to live directly in CDROutputStream.grow.

    // Recall that the byteBuffer size is 'really' the limit or
    // buflen.

    int newLength = byteBuffer.limit() * 2;

    while (position() + needed >= newLength) {
      newLength = newLength * 2;
    }

    ByteBufferPool byteBufferPool = orb.getByteBufferPool();
    ByteBuffer newBB = byteBufferPool.getByteBuffer(newLength);

    if (debug) {
      // print address of ByteBuffer just gotten
      int newbbAddress = System.identityHashCode(newBB);
      StringBuffer sb = new StringBuffer(80);
      sb.append("growBuffer() - got ByteBuffer id (");
      sb.append(newbbAddress).append(") from ByteBufferPool.");
      String msgStr = sb.toString();
      dprint(msgStr);
    }

    byteBuffer.position(0);
    newBB.put(byteBuffer);

    // return 'old' byteBuffer reference to the ByteBuffer pool
    if (debug) {
      // print address of ByteBuffer being released
      int bbAddress = System.identityHashCode(byteBuffer);
      StringBuffer sb = new StringBuffer(80);
      sb.append("growBuffer() - releasing ByteBuffer id (");
      sb.append(bbAddress).append(") to ByteBufferPool.");
      String msgStr2 = sb.toString();
      dprint(msgStr2);
    }
    byteBufferPool.releaseByteBuffer(byteBuffer);

    // update the byteBuffer with a larger ByteBuffer
    byteBuffer = newBB;

    // limit and buflen must be set to newLength.
    buflen = newLength;
    byteBuffer.limit(buflen);
  }

  public String toString() {
    StringBuffer str = new StringBuffer("ByteBufferWithInfo:");

    str.append(" buflen = " + buflen);
    str.append(" byteBuffer.limit = " + byteBuffer.limit());
    str.append(" index = " + index);
    str.append(" position = " + position());
    str.append(" needed = " + needed);
    str.append(" byteBuffer = " + (byteBuffer == null ? "null" : "not null"));
    str.append(" fragmented = " + fragmented);

    return str.toString();
  }

  protected void dprint(String msg) {
    ORBUtility.dprint("ByteBufferWithInfo", msg);
  }
}
